home *** CD-ROM | disk | FTP | other *** search
/ FishMarket 1.0 / FishMarket v1.0.iso / fishies / 526-550 / disk_535 / keymacro / mrarpfile.c < prev    next >
C/C++ Source or Header  |  1992-05-06  |  17KB  |  677 lines

  1. /*  File support routines to complement ARP.
  2.  *  Author: Mark R. Rinfret
  3.  *          Usenet:     mrr@amanpt1.Newport.RI.US
  4.  *          BIX:        markr
  5.  *          CIS:        72017, 136 (good luck!)
  6.  *
  7.  *          348 Indian Avenue
  8.  *          Portsmouth, RI 02871
  9.  *          401-846-7639 (home)
  10.  *          401-849-9390 (work)       
  11.  *
  12.  *  This package was written primarily because of _one_ missing element
  13.  *  in ARP: FGets. ARGH! ARPFFFT! It extends ARP by adding buffering to
  14.  *  the basic file type (FileHandle) and defining a new type, named
  15.  *  ARPFileHandle (hope this is OK with the ARP guys). Also, I've used the
  16.  *  convention of embedding ARP (vs. Arp in MicroSmith's stuff) in all type
  17.  *  and function names to (hopefully) avoid naming collisions.
  18.  *
  19.  *  This package (as far as I am concerned) is public domain. Use it any
  20.  *  way you like. Some comments on the "MR" prefix: it isn't short for
  21.  *  "Mister", it comprises the initials of my first and last names;
  22.  *  I use it primarily to avoid name collisions with stuff by other
  23.  *  authors. If any other authors whose initials are "MR" start doing
  24.  *  this, we'll probably have to appeal to Electronic Arts to dole out
  25.  *  "author codes" along the lines of those issued for IFF :-). I hereby
  26.  *  stake a claim to 'MRR_'.
  27.  *
  28.  *  A word of warning:
  29.  *
  30.  *  DO NOT INTERMIX STANDARD AMIGADOS FILE SUPPORT CALLS WITH CALLS TO
  31.  *  THIS PACKAGE ON FILES USING THIS FILE METHOD!
  32.  *
  33.  *  Obviously, the system doesn't know about the buffering added here
  34.  *  and will cause unpredictable results (unless, of course, you
  35.  *  take care to maintain the ARPFileHandle information).
  36.  */
  37.  
  38. /*  Olsen: In order to adapt the code to ANSI some declarations and
  39.  *         function calls were rearranged. I also fixed two parts of
  40.  *         code which appeared not to work correctly.
  41.  */
  42.  
  43.     /* Skip prototypes. */
  44.  
  45. #define _MRARPFILE_PRIVATE 1
  46.  
  47. #include "MRARPFile.h"
  48.  
  49. /* StoreTracker is my attempt to fix an apparent bug in ARP 1.3. I have
  50.  * found that the LastTracker kludge (via IoErr()) doesn't work reliably.
  51.  * StoreTracker simply stuffs A1 into D0 and returns . It *MUST* be
  52.  * called immediately after any ARP call which allocates a tracker to
  53.  * assure that the value is correct.
  54.  */
  55.  
  56. struct DefaultTracker *    StoreTracker(VOID);
  57.  
  58. char *            FGetsARP(char *s,LONG length,ARPFileHandle *file);
  59. static LONG        FillARPFileBuffer(ARPFileHandle *file);
  60. static LONG        FlushARPFileBuffer(ARPFileHandle *file);
  61. LONG            FPutsARP(char *s,ARPFileHandle *file);
  62. LONG            ReadARPFile(ARPFileHandle *file,UBYTE *buffer,LONG length);
  63. LONG            CloseARPFile(ARPFileHandle *file);
  64. ARPFileHandle *        OpenARPFile(char *name,LONG accessMode,LONG bytes);
  65. LONG            SeekARPFile(ARPFileHandle *file,LONG position,LONG mode);
  66. LONG            WriteARPFile(ARPFileHandle *file,UBYTE *buffer,LONG length);
  67.  
  68. /*  FUNCTION
  69.  *      FGetsARP - get string from a buffered ARP file.
  70.  *
  71.  *  SYNOPSIS
  72.  *      char *FGetsARP(s, length, file)
  73.  *                     UBYTE         *s;
  74.  *                     LONG           length;
  75.  *                     ARPFileHandle *file;
  76.  *
  77.  *  DESCRIPTION
  78.  *      FGetsARP models the behavior of the "standard" fgets, except that
  79.  *      it is used with a buffered ARP file. The data is read from <file>
  80.  *      and transfered to string <s>. Up to length-1 characters will be
  81.  *      read. Reading is also terminated upon receipt of a newline
  82.  *      or detection of end-of-file. 
  83.  *
  84.  *      If the <file> was opened without a buffer, one of MaxInputBuf
  85.  *      bytes will be allocated.
  86.  *
  87.  *      If end of file or an error is detected, the return value will be
  88.  *      NULL. Otherwise, the return value will be <s>. <s> will be 
  89.  *      terminated with a null byte.
  90.  */
  91.  
  92. char *
  93. FGetsARP(char *s,LONG length,ARPFileHandle *file)
  94. {
  95.     struct DefaultTracker    *tracker;
  96.     LONG             bytesNeeded = length - 1;
  97.     LONG             bytesRead = 0;
  98.     char             c;
  99.     char            *s1 = s;
  100.  
  101.     /* Set string initially empty to protect user from failure to check
  102.      * return value.
  103.      */
  104.  
  105.     *s1 = '\0';    
  106.  
  107.     if(file -> mode != MODE_OLDFILE)
  108.         file -> lastError = ERROR_READ_PROTECTED;
  109.          
  110.     if(file -> lastError)
  111.     {
  112. dangit:        return(NULL);
  113.     }
  114.  
  115.         /* Ohmigosh! No buffer? */
  116.  
  117.     if(!file -> buf)
  118.     {
  119.         file -> buf = ArpAllocMem(MaxInputBuf, MEMF_CLEAR | MEMF_PUBLIC);
  120.  
  121.         tracker = StoreTracker();
  122.  
  123.         if(!file -> buf)
  124.         {
  125.             file -> lastError = ERROR_NO_FREE_STORE;
  126.             goto dangit;
  127.         }
  128.  
  129.         file -> bufSize        = MaxInputBuf;    /* bufLength, bufPos are zero. */
  130.         file -> bufTracker    = tracker;
  131.     }
  132.  
  133.     while(bytesNeeded)
  134.     {
  135.         if(file -> bufPos >= file -> bufLength)
  136.         {
  137.             if(FillARPFileBuffer(file) < 0)
  138.                 goto dangit;
  139.  
  140.             if(!file -> bufLength)
  141.                 break;
  142.         }
  143.  
  144.         c = file -> buf[file -> bufPos++];
  145.  
  146.         ++bytesRead;
  147.  
  148.         if(c == '\n')
  149.             break;
  150.  
  151.         *s1++ = c;
  152.  
  153.         --bytesNeeded; 
  154.     }
  155.  
  156.     *s1 = '\0';
  157.  
  158.     return(bytesRead ? s : NULL);
  159. }
  160.  
  161. /*  FUNCTION
  162.  *      FillARPFileBuffer - read data into ARPFile buffer.
  163.  *
  164.  *  SYNOPSIS
  165.  *      static LONG FillARPFileBuffer(file)
  166.  *                                    ARPFileHandle *file;
  167.  *
  168.  *  DESCRIPTION
  169.  *      Attempt to fill the buffer associated with <file> by reading
  170.  *      data from the associated external file. The return value will
  171.  *      be one of the following:
  172.  *          >0  => number of bytes read
  173.  *           0  => end of file
  174.  *          -1  => a Bad Thing happened (error code in file -> lastError) 
  175.  *
  176.  *      Note: this is a local routine and is thus declared as "static".
  177.  *      Please inform the author if this is a hardship.
  178.  */
  179.  
  180. static LONG
  181. FillARPFileBuffer(ARPFileHandle *file)
  182. {
  183.     /* Remember where we were. The user might want to try error
  184.      * recovery. Of course, this probably isn't enough info, but
  185.      * it's a start.
  186.      */
  187.  
  188.     file -> lastPosition    = Seek(file -> fh, 0L, OFFSET_CURRENT);
  189.     file -> bufPos        = 0;
  190.     file -> bufLength    = Read(file -> fh, file -> buf, file -> bufSize);
  191.  
  192.         /* We got trubble! */
  193.  
  194.     if(file -> bufLength == -1)
  195.         file -> lastError = IoErr();
  196.     else
  197.     {
  198.         if(!file -> bufLength)
  199.             file -> endOfFile = TRUE;
  200.     }
  201.  
  202.     return(file -> bufLength);
  203.  
  204. /*  FUNCTION
  205.  *      FlushARPFileBuffer - write file buffer contents to disk.
  206.  *
  207.  *  SYNOPSIS
  208.  *      static LONG FlushARPFileBuffer(file)
  209.  *                                     ARPFileHandle *file;
  210.  *  DESCRIPTION
  211.  *      FlushARPFileBuffer writes the contents of <file>'s buffer
  212.  *      (if any) to disk and resets the buffer information. The
  213.  *      return value may be any of:
  214.  *
  215.  *          >0 =>   number of bytes written
  216.  *           0 =>   nothing in buffer
  217.  *          <0 =>   negated error code
  218.  *
  219.  *      Note: it is assumed that this function will only be used locally
  220.  *      and therefore need not be public. If you disagree, please contact
  221.  *      the author.
  222.  */
  223.  
  224. static LONG
  225. FlushARPFileBuffer(ARPFileHandle *file)
  226. {
  227.     LONG bytesWritten = 0;
  228.  
  229.     /* This operation is only allowed for output files. */
  230.  
  231.     if(file -> mode != MODE_NEWFILE)
  232.     {
  233.         file -> lastError = ERROR_WRITE_PROTECTED;
  234.  
  235. badstuff:    return(-file -> lastError);
  236.     }
  237.  
  238.     if(file -> lastError)
  239.         goto badstuff; /* Residual error? */
  240.  
  241.     if(file -> bufLength)
  242.     {
  243.         file -> lastPosition = Seek(file -> fh, 0L, OFFSET_CURRENT);
  244.  
  245.         bytesWritten = Write(file -> fh, file -> buf, file -> bufLength);
  246.  
  247.         if(bytesWritten != file -> bufLength)
  248.         {
  249.             file -> lastError = IoErr();
  250.             goto badstuff;
  251.         }
  252.         else
  253.         {
  254.             file -> bufLength    = 0;
  255.             file -> bufPos        = 0;
  256.         }
  257.     }
  258.  
  259.     return(bytesWritten);
  260. }
  261.  
  262. /*  FUNCTION
  263.  *      FPutsARP - write a string to a buffered ARP file.
  264.  *
  265.  *  SYNOPSIS
  266.  *      LONG FPutsARP(s, file)
  267.  *                    char          *s;
  268.  *                    ARPFileHandle *file;
  269.  *
  270.  *  DESCRIPTION
  271.  *      FPutsARP writes the contents of string <s> to the specified <file>.
  272.  *      If successful, it returns 0. On failure, it returns the negated
  273.  *      system error code. 
  274.  */
  275.  
  276. LONG
  277. FPutsARP(char *s,ARPFileHandle *file)
  278. {
  279.     LONG     bytesLeft, bytesUsed;
  280.     char    *s1 = s;
  281.  
  282.     if(file -> mode != MODE_NEWFILE) 
  283.         file -> lastError = ERROR_WRITE_PROTECTED;
  284.  
  285.     if(file -> lastError)
  286.     {
  287. shucks:        return(-file -> lastError);
  288.     }
  289.  
  290.     bytesLeft = strlen(s);
  291.  
  292.     /* Attempt to be smart about this transfer. Copy the string to the
  293.      * buffer in chunks. There is a possibility that the string is bigger
  294.      * than the size of the buffer.
  295.      */
  296.  
  297.     while(bytesLeft)
  298.     {
  299.         if(file -> bufPos >= file -> bufSize)
  300.         {
  301.             if(FlushARPFileBuffer(file) <= 0)
  302.                 goto shucks;
  303.         }
  304.  
  305.         bytesUsed = MIN(file -> bufSize - file -> bufPos, bytesLeft);
  306.  
  307.         CopyMem(s1, &file -> buf[file -> bufPos], bytesUsed);
  308.  
  309.         s1 += bytesUsed;
  310.  
  311.         file -> bufLength = (file -> bufPos += bytesUsed);
  312.  
  313.         bytesLeft -= bytesUsed;
  314.     }
  315.  
  316.     return(0);
  317. }
  318.  
  319. /*  FUNCTION
  320.  *      ReadARPFile - read from a buffered ARP file.
  321.  *
  322.  *  SYNOPSIS
  323.  *      LONG ReadARPFile(file, buffer, length)
  324.  *                       ARPFile *file;
  325.  *                       UBYTE   *buffer;
  326.  *                       LONG    length;
  327.  *
  328.  *  DESCRIPTION
  329.  *      ReadARPFile attempts to read <length> bytes from <file>, transferring
  330.  *      the data to <buffer>. 
  331.  *
  332.  *      The return value may be any of the following:
  333.  *
  334.  *          >0      number of bytes transferred
  335.  *          0       end of file
  336.  *         <0       negated error code
  337.  *
  338.  *      Note: if the lastError field of the <file> descriptor contains a
  339.  *            non-zero value, its negated value will be returned and no
  340.  *            attempt will be made to read the file. If you attempt error
  341.  *            recovery, you must clear this field to zero.
  342.  */
  343.  
  344. LONG
  345. ReadARPFile(ARPFileHandle *file,UBYTE *buffer,LONG length)
  346. {
  347.     LONG    bytesLeft;
  348.     LONG    bytesRead = 0;
  349.     LONG    needBytes = length;
  350.     LONG    pos = 0;
  351.     LONG    usedBytes = 0;
  352.  
  353.         /* Prevent read if this file opened for writing. */
  354.  
  355.     if(file -> mode != MODE_OLDFILE)
  356.         file -> lastError = ERROR_READ_PROTECTED;
  357.  
  358.         /* Have residual error? */
  359.  
  360.     if(file -> lastError)
  361.     {
  362. boofar:        return(-file -> lastError);
  363.     }
  364.  
  365.         /* No buffer? */
  366.  
  367.     if(!file -> buf )
  368.     {
  369.         bytesRead = Read(file -> fh, buffer, length);
  370.  
  371.         if(bytesRead == -1)
  372.         {
  373.             file -> lastError = IoErr();
  374.             goto boofar;
  375.         }
  376.     }
  377.     else
  378.     {
  379.         while(needBytes)
  380.         {
  381.             if(file -> bufLength - file -> bufPos <= 0)
  382.             {
  383.                 if(FillARPFileBuffer(file) == -1)
  384.                     goto boofar;
  385.  
  386.                 if(!file -> bufLength)
  387.                     break;
  388.             }
  389.  
  390.             bytesLeft = file -> bufLength - file -> bufPos;
  391.  
  392.             usedBytes = MIN(bytesLeft, length);
  393.  
  394.             CopyMem(&file -> buf[file -> bufPos], &buffer[pos], usedBytes);
  395.  
  396.             file -> bufPos += usedBytes;
  397.  
  398.             pos += usedBytes;
  399.  
  400.             bytesRead += usedBytes;
  401.             needBytes -= usedBytes;
  402.         }
  403.     }
  404.  
  405.     return(bytesRead);
  406. }
  407.  
  408. /*  FUNCTION
  409.  *      CloseARPFile - close buffered ARP file.
  410.  *
  411.  *  SYNOPSIS
  412.  *      LONG CloseARPFile(file)
  413.  *                        ARPFileHandle *file;
  414.  *
  415.  *  DESCRIPTION
  416.  *      CloseARPFile closes the file described by <file> which MUST have
  417.  *      been opened by OpenARPFile. It releases all tracked items
  418.  *      associated with <file>, as well.
  419.  *
  420.  *      The return value is only meaningful for output files. If, upon
  421.  *      flushing the buffer, a write error is detected, a system error
  422.  *      code (ERROR_DISK_FULL, etc.) will be returned.
  423.  */
  424.  
  425. LONG
  426. CloseARPFile(ARPFileHandle *file)
  427. {
  428.     LONG result = 0;
  429.  
  430.         /* Just in case... */
  431.  
  432.     if(file)
  433.     {
  434.         if(file -> fileTracker)
  435.         {
  436.             /* Any left-over stuff in the buffer? If so, we must flush
  437.              * it to disk. However, if an error was detected in the
  438.              * previous operation, punt. 
  439.              */
  440.  
  441.             if((file -> mode == MODE_NEWFILE) && ! file -> lastError && file -> bufLength)
  442.             {
  443.                 if(Write(file -> fh, file -> buf, file -> bufLength) != file -> bufLength)
  444.                     result = IoErr();
  445.             }
  446.  
  447.             FreeTrackedItem(file -> fileTracker);
  448.         }
  449.  
  450.         if(file -> bufTracker)
  451.             FreeTrackedItem(file -> bufTracker);
  452.  
  453.         FreeTrackedItem(file -> myTracker);
  454.     }
  455.  
  456.     return(result);
  457. }
  458.  
  459. /*  FUNCTION
  460.  *      OpenARPFile - open a buffered ARP file
  461.  *
  462.  *  SYNOPSIS
  463.  *      struct MRARPFile *OpenARPFile(name, accessMode, bytes)
  464.  *                                    char *name;
  465.  *                                    LONG accessMode, bytes;
  466.  *
  467.  *  DESCRIPTION
  468.  *      OpenARPFile opens the file <name>, with the given <accessMode>
  469.  *      (MODE_OLDFILE, MODE_NEWFILE only!) for buffered access. The
  470.  *      size of the local buffer is specified by <bytes>.
  471.  *
  472.  *      A zero value for <bytes> is OK and is sometimes appropriate, as
  473.  *      when performing file copy operations, etc. However, if a file
  474.  *      opened with a zero length buffer is then passed to the
  475.  *      FGetsARP function, "spontaneous buffer allocation" will occur.
  476.  *      No biggy, really, just something you should know. The ARP constant,
  477.  *      MaxInputBuf will be used for the buffer size, in this case.
  478.  *
  479.  *      If successful, a pointer to the file tracking structure is 
  480.  *      returned. Otherwise, the return value will be NULL.
  481.  *
  482.  *      OpenARPFile uses full resource tracking for all information
  483.  *      associated with the file.
  484.  *
  485.  */
  486.  
  487. ARPFileHandle *
  488. OpenARPFile(char *name,LONG accessMode,LONG bytes)
  489. {
  490.     struct DefaultTracker    *lastTracker;
  491.     ARPFileHandle        *theFile = NULL;
  492.     BPTR             fh;
  493.  
  494.         /* This package does not support READ/WRITE access! */
  495.  
  496.     if((accessMode != MODE_OLDFILE) && (accessMode != MODE_NEWFILE))
  497.         return(NULL);
  498.  
  499.     /* 
  500.      * Note: I'm using ArpAllocMem vs. ArpAlloc here because it appears
  501.      * that ArpAlloc gives me a bad tracker pointer (?).
  502.      */
  503.  
  504.     theFile = ArpAllocMem((LONG) sizeof(ARPFileHandle), MEMF_PUBLIC | MEMF_CLEAR);
  505.  
  506.     lastTracker = StoreTracker();
  507.  
  508.     if(theFile)
  509.     {
  510.         theFile -> myTracker = lastTracker;
  511.         theFile -> mode = accessMode;
  512.  
  513.         fh = ArpOpen(name, accessMode);
  514.  
  515.         lastTracker = StoreTracker();
  516.  
  517.         if(!fh)
  518.         {
  519. fungu:            CloseARPFile(theFile);    /* Don't worry - it's "smart". */
  520.             theFile = NULL;
  521.  
  522.             return(NULL);        /* This one was missing -Olsen */
  523.         }
  524.  
  525.         theFile -> fileTracker    = lastTracker;
  526.         theFile -> fh        = fh;
  527.  
  528.             /* Does user want a buffer? */
  529.  
  530.         if(bytes)
  531.         {
  532.             theFile -> buf = ArpAllocMem(bytes, MEMF_PUBLIC | MEMF_CLEAR);
  533.  
  534.             lastTracker = StoreTracker();
  535.  
  536.             if(!theFile -> buf)
  537.                 goto fungu;
  538.  
  539.             theFile -> bufSize    = bytes;
  540.             theFile -> bufTracker    = lastTracker;
  541.         }
  542.     }
  543.  
  544.     return(theFile);
  545. }
  546.  
  547. /*  FUNCTION
  548.  *      SeekARPFile - move to new logical position in file.
  549.  *
  550.  *  SYNOPSIS
  551.  *      LONG SeekARPFile(file, position, mode)
  552.  *                       ARPFileHandle *file;
  553.  *                       LONG           position;
  554.  *                       LONG           mode;
  555.  *
  556.  *  DESCRIPTION
  557.  *      SeekARPFile attempts to position the <file> to a new logical
  558.  *      position or report it's current position. The <position>
  559.  *      parameter represets a signed offset. The <mode> parameter may
  560.  *      be one of:
  561.  *
  562.  *          OFFSET_BEGINNING (-1) => from beginning of file
  563.  *          OFFSET_CURRENT (0)    => from current position
  564.  *          OFFSET_END (-1)       => from end of file
  565.  *
  566.  *      On output files, the current buffer contents, if any, are
  567.  *      written to disk.
  568.  *
  569.  *      The return value will either be a positive displacement >=0) or
  570.  *      a negated system error code.
  571.  */
  572.  
  573. LONG
  574. SeekARPFile(ARPFileHandle *file,LONG position,LONG mode)
  575. {
  576.     LONG newPosition;
  577.  
  578.     if(file -> mode == MODE_NEWFILE && file -> bufLength)
  579.     {
  580.         if(FlushARPFileBuffer(file) < 0)
  581.         {
  582. farboo:            return(-file -> lastError);
  583.         }
  584.     }
  585.  
  586.         /* Remember our last position. Seek(file,...) fixed
  587.          * to use Seek(file -> fh,...) -Olsen.
  588.          */
  589.  
  590.     file -> lastPosition = Seek(file -> fh, 0L, OFFSET_CURRENT);
  591.  
  592.     if((newPosition = Seek(file -> fh, position, mode)) == -1)
  593.     {
  594.         file -> lastError = IoErr();
  595.         goto farboo;
  596.     }
  597.  
  598.     return(newPosition);
  599. }  
  600.  
  601. /*  FUNCTION
  602.  *      WriteARPFile - write data to a buffered ARP file.
  603.  *
  604.  *  SYNOPSIS
  605.  *      LONG WriteARPFile(file, buffer, length)
  606.  *                        ARPFileHandle *file;
  607.  *                        UBYTE         *buffer;
  608.  *                        LONG          length;
  609.  *
  610.  *  DESCRIPTION
  611.  *      WriteARPFile attempts to write <length> bytes from <buffer> to
  612.  *      the buffered ARP file, <file>. The file MUST have been opened
  613.  *      with OpenARPFile for MODE_NEWFILE access.
  614.  *
  615.  *      WriteARPFile will not write to a file if the lastError field is
  616.  *      non-zero, or if the file was opened for input.
  617.  *
  618.  *      If successful, WriteARPFile will return the <length> parameter.
  619.  *      Otherwise, it will return a negated system error code.
  620.  */
  621.  
  622. LONG
  623. WriteARPFile(ARPFileHandle *file,UBYTE *buffer,LONG length)
  624. {
  625.     LONG    bufferBytes;
  626.     LONG    bytesLeft = length;
  627.     LONG    pos = 0;
  628.     LONG    usedBytes = 0;
  629.  
  630.     if(file -> mode != MODE_NEWFILE)
  631.         file -> lastError = ERROR_WRITE_PROTECTED;
  632.  
  633.         /* Catches mode and residual errors. */
  634.  
  635.     if(file -> lastError)
  636.     {
  637. sumbidge:    return(-file -> lastError);
  638.     }
  639.  
  640.         /* No buffer? */
  641.  
  642.     if(!file -> buf)
  643.     {
  644.         if(Write(file -> fh, buffer, length) != length)
  645.         {
  646.             file -> lastError = IoErr();
  647.             goto sumbidge;
  648.         }
  649.     }
  650.     else
  651.     {
  652.         while(bytesLeft)
  653.         {
  654.                 /* Need to flush the file's buffer? */
  655.  
  656.             if(file -> bufPos >= file -> bufSize)
  657.                 if(FlushARPFileBuffer(file) < 0)
  658.                     goto sumbidge;
  659.  
  660.             bufferBytes = file -> bufSize - file -> bufPos;
  661.  
  662.             usedBytes = MIN(bufferBytes, bytesLeft);
  663.  
  664.             CopyMem(&buffer[pos], &file -> buf[file -> bufPos], usedBytes);
  665.  
  666.             file -> bufLength = (file -> bufPos += usedBytes);
  667.  
  668.             pos += usedBytes;
  669.  
  670.             bytesLeft -= usedBytes;
  671.         }
  672.     }
  673.  
  674.     return(length);
  675. }
  676.